import os
from typing import Callable, Union, List

from scripts.Log import Log
from scripts.actions.Action import Action
from scripts.actions.BackupAdbBackupAction import BackupAdbBackupAction
from scripts.actions.BackupAppAction import BackupAppAction
from scripts.actions.BackupAppDataAction import BackupAppDataAction
from scripts.actions.BackupMiscAction import BackupMiscAction
from scripts.actions.BackupSystemAction import BackupSystemAction
from scripts.actions.BackupUserAction import BackupUserAction
from scripts.actions.CleanupAction import CleanupAction
from scripts.actions.Context import Context
from scripts.actions.PrepareAction import PrepareAction
from scripts.device.AdbDevice import AdbDevice


class BackupRestore(object):
    DEVICE_TEMP_DIR = '/data/local/tmp/backup_tmp'

    STEP_PREPARE = PrepareAction.KEY
    STEP_BACKUP_USER = BackupUserAction.KEY
    STEP_BACKUP_APP = BackupAppAction.KEY
    STEP_BACKUP_ADB_BACKUP = BackupAdbBackupAction.KEY
    STEP_BACKUP_APP_DATA = BackupAppDataAction.KEY
    STEP_BACKUP_SYSTEM = BackupSystemAction.KEY
    STEP_BACKUP_MISC = BackupMiscAction.KEY
    STEP_CLEANUP = CleanupAction.KEY

    class Step(object):
        def __init__(self, action):
            self._action = action

        def key(self):
            return self._action.KEY

        def display_name(self):
            return self._action.DISPLAY_NAME

        def create_action(self, context: Context):
            return self._action(context)

    STEPS = {
        STEP_PREPARE: Step(PrepareAction),
        STEP_BACKUP_USER: Step(BackupUserAction),
        STEP_BACKUP_APP: Step(BackupAppAction),
        STEP_BACKUP_ADB_BACKUP: Step(BackupAdbBackupAction),
        STEP_BACKUP_APP_DATA: Step(BackupAppDataAction),
        STEP_BACKUP_SYSTEM: Step(BackupSystemAction),
        STEP_BACKUP_MISC: Step(BackupMiscAction),
        STEP_CLEANUP: Step(CleanupAction)
    }

    def __init__(self, sn: Union[None, str], host_dir: str, steps: List[str]):
        self._device_temp_dir = BackupRestore.DEVICE_TEMP_DIR
        self._sn = sn
        self._host_dir = host_dir
        self._steps = steps

    def make_action_list(self) -> List[Action]:
        context = Context(self._sn)
        context.set_device_temp_dir(self._device_temp_dir)
        context.set_host_dir(self._host_dir)

        return [BackupRestore.STEPS[step].create_action(context) for step in self._steps]

    def backup(self, progress: Union[None, Callable[[int, int, str, int, int, str], None]] = None):
        # get current plugged device sn if sn is None
        if self._sn is None:
            self._sn = AdbDevice.wait_for_device().sn()

        os.makedirs(self._host_dir, exist_ok=True)
        action_list = self.make_action_list()

        cur_prog = 0
        max_prog = len(action_list)
        msg = action_list[cur_prog].key()

        def prog(sub_cur_prog: int, sub_max_prog: int, sub_msg: str):
            nonlocal progress
            nonlocal cur_prog
            nonlocal max_prog
            nonlocal msg

            if progress:
                progress(cur_prog, max_prog, msg, sub_cur_prog, sub_max_prog, sub_msg)

        # actions
        for action in action_list:
            action.backup(prog)

            cur_prog += 1
            if cur_prog < len(action_list):
                msg = action_list[cur_prog].key()

        Log.i('Backup finished!!')

    def restore(self, progress: Union[None, Callable[[int, int, str, int, int, str], None]] = None):
        # get current plugged device sn if sn is None
        if self._sn is None:
            self._sn = AdbDevice.wait_for_device().sn()

        if not os.path.exists(self._host_dir):
            Log.e('backup of [%s] not found. Please do backup first' % self._sn)
            return

        action_list = self.make_action_list()

        cur_prog = 0
        max_prog = len(action_list)
        msg = action_list[cur_prog].key()

        def prog(sub_cur_prog: int, sub_max_prog: int, sub_msg: str):
            nonlocal progress
            nonlocal cur_prog
            nonlocal max_prog
            nonlocal msg

            if progress:
                progress(cur_prog, max_prog, msg, sub_cur_prog, sub_max_prog, sub_msg)

        for action in self.make_action_list():
            action.restore(prog)

            cur_prog += 1
            if cur_prog < len(action_list):
                msg = action_list[cur_prog].key()

        Log.i('Restore finished!!')
